Skip to content

feat(grm,case): add case management modules and sync GRM with stable#68

Merged
gonzalesedwin1123 merged 12 commits into19.0from
push-modules-set-to-stable
Mar 5, 2026
Merged

feat(grm,case): add case management modules and sync GRM with stable#68
gonzalesedwin1123 merged 12 commits into19.0from
push-modules-set-to-stable

Conversation

@gonzalesedwin1123
Copy link
Member

@gonzalesedwin1123 gonzalesedwin1123 commented Mar 5, 2026

Add 12 new modules: spp_case_base, spp_case_cel, spp_case_demo, spp_case_entitlements, spp_case_graduation, spp_case_programs, spp_case_registry, spp_case_session, spp_grm_case_link, spp_grm_cel, spp_grm_programs, and spp_grm_registry.

Update spp_grm and spp_grm_demo to match stable upstream: add ticket workflow action buttons, refine officer record rules with separate create permission, rename privilege_grm_user to privilege_grm, convert Html fields to Text, and default ticket assignment to current user.


Note

Medium Risk
Adds a large new functional module with new record rules/ACLs and scheduled automation; misconfiguration could impact data visibility and operational behavior, though changes are largely additive and covered by tests.

Overview
Introduces a new Odoo addon, spp_case_base, providing core case-management models (cases, assessments, intervention plans/interventions, visits, notes, referrals, and supporting configuration like stages/types/teams) plus a review-reminder cron (_cron_check_reviews) and basic workflow actions (close/reopen, plan approval/versioning, assessment review).

Adds the module’s security posture end-to-end (domain privilege + tiered groups, ir.model.access.csv, record rules, and compliance spec) and a comprehensive test suite covering model behaviors and access rules. Separately adds codecov.yml for PR/patch coverage gating with carry-forward flags and updates pre-commit semgrep exclusions to skip additional demo modules.

Written by Cursor Bugbot for commit 3fce171. This will update automatically on new commits. Configure here.

Add 12 new modules: spp_case_base, spp_case_cel, spp_case_demo,
spp_case_entitlements, spp_case_graduation, spp_case_programs,
spp_case_registry, spp_case_session, spp_grm_case_link, spp_grm_cel,
spp_grm_programs, and spp_grm_registry.

Update spp_grm and spp_grm_demo to match stable upstream: add ticket
workflow action buttons, refine officer record rules with separate
create permission, rename privilege_grm_user to privilege_grm, convert
Html fields to Text, and default ticket assignment to current user.
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 4 potential issues.

Bugbot Free Tier Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Autofix Details

Bugbot Autofix prepared fixes for all 4 issues found in the latest run.

  • ✅ Fixed: Missing record rules for assessment model
    • Added three record rules (worker/supervisor/manager) for spp.case.assessment model following the same pattern as other case-related models to enforce data isolation.
  • ✅ Fixed: Cron creates duplicate overdue review activities daily
    • Added deduplication check in _cron_check_reviews to search for existing overdue activities before creating new ones, matching the pattern used for upcoming reviews.
  • ✅ Fixed: Stage requirement fields defined but never enforced
    • Added validation checks in _check_stage_requirements for is_requires_assessment (verifying completed assessments exist) and is_requires_approval (verifying supervisor permissions).
  • ✅ Fixed: Plan approval skips state validation check
    • Added state validation in action_approve to ensure plans can only be approved from pending_approval state, preventing workflow bypass.

Create PR

Or push these changes by commenting:

@cursor push c2b299c69d
Preview (c2b299c69d)
diff --git a/spp_case_base/models/case.py b/spp_case_base/models/case.py
--- a/spp_case_base/models/case.py
+++ b/spp_case_base/models/case.py
@@ -399,6 +399,17 @@
             if case.stage_id.is_requires_plan and not case.has_active_plan:
                 raise ValidationError(f"Stage '{case.stage_id.name}' requires an approved intervention plan.")
 
+            # Check if assessment is required
+            if case.stage_id.is_requires_assessment:
+                completed_assessment = case.assessment_ids.filtered(lambda a: a.state in ["completed", "reviewed"])
+                if not completed_assessment:
+                    raise ValidationError(f"Stage '{case.stage_id.name}' requires a completed assessment.")
+
+            # Check if approval is required
+            if case.stage_id.is_requires_approval:
+                if not self.env.user.has_group("spp_case_base.group_case_supervisor"):
+                    raise ValidationError(f"Stage '{case.stage_id.name}' requires supervisor approval.")
+
             # Check minimum intensity level
             if case.stage_id.min_intensity:
                 if int(case.intensity_level) < int(case.stage_id.min_intensity):
@@ -449,15 +460,26 @@
         )
 
         for case in overdue_cases:
-            # Create activity for case worker
-            case.activity_schedule(
-                "mail.mail_activity_data_todo",
-                date_deadline=today,
-                summary=_("Case review overdue"),
-                note=_("Case %s is due for review. Last review: %s") % (case.name, case.last_review_date or _("Never")),
-                user_id=case.case_worker_id.id,
+            # Check if activity already exists
+            existing = self.env["mail.activity"].search(
+                [
+                    ("res_model", "=", "spp.case"),
+                    ("res_id", "=", case.id),
+                    ("summary", "ilike", "review overdue"),
+                ],
+                limit=1,
             )
 
+            if not existing:
+                # Create activity for case worker
+                case.activity_schedule(
+                    "mail.mail_activity_data_todo",
+                    date_deadline=today,
+                    summary=_("Case review overdue"),
+                    note=_("Case %s is due for review. Last review: %s") % (case.name, case.last_review_date or _("Never")),
+                    user_id=case.case_worker_id.id,
+                )
+
         # Find cases approaching review (3 days warning)
         warning_date = today + timedelta(days=3)
         upcoming_cases = self.search(

diff --git a/spp_case_base/models/case_intervention_plan.py b/spp_case_base/models/case_intervention_plan.py
--- a/spp_case_base/models/case_intervention_plan.py
+++ b/spp_case_base/models/case_intervention_plan.py
@@ -179,6 +179,8 @@
     def action_approve(self):
         """Approve the plan."""
         for plan in self:
+            if plan.state != "pending_approval":
+                raise ValidationError("Only plans pending approval can be approved.")
             plan.write(
                 {
                     "state": "approved",

diff --git a/spp_case_base/security/rules.xml b/spp_case_base/security/rules.xml
--- a/spp_case_base/security/rules.xml
+++ b/spp_case_base/security/rules.xml
@@ -207,5 +207,39 @@
         <field name="perm_create" eval="True"/>
         <field name="perm_unlink" eval="True"/>
     </record>
+
+    <!-- Record Rules for spp.case.assessment -->
+    <record id="rule_spp_case_assessment_worker" model="ir.rule">
+        <field name="name">Case Assessment: Worker Own Case Assessments Only</field>
+        <field name="model_id" ref="model_spp_case_assessment"/>
+        <field name="domain_force">[('case_id.case_worker_id', '=', user.id)]</field>
+        <field name="groups" eval="[Command.link(ref('group_case_worker'))]"/>
+        <field name="perm_read" eval="True"/>
+        <field name="perm_write" eval="True"/>
+        <field name="perm_create" eval="True"/>
+        <field name="perm_unlink" eval="False"/>
+    </record>
+
+    <record id="rule_spp_case_assessment_supervisor" model="ir.rule">
+        <field name="name">Case Assessment: Supervisor Team Case Assessments</field>
+        <field name="model_id" ref="model_spp_case_assessment"/>
+        <field name="domain_force">['|', ('case_id.supervisor_id', '=', user.id), ('case_id.team_id.supervisor_id', '=', user.id)]</field>
+        <field name="groups" eval="[Command.link(ref('group_case_supervisor'))]"/>
+        <field name="perm_read" eval="True"/>
+        <field name="perm_write" eval="True"/>
+        <field name="perm_create" eval="True"/>
+        <field name="perm_unlink" eval="False"/>
+    </record>
+
+    <record id="rule_spp_case_assessment_manager" model="ir.rule">
+        <field name="name">Case Assessment: Manager All Case Assessments</field>
+        <field name="model_id" ref="model_spp_case_assessment"/>
+        <field name="domain_force">[(1, '=', 1)]</field>
+        <field name="groups" eval="[Command.link(ref('group_case_manager'))]"/>
+        <field name="perm_read" eval="True"/>
+        <field name="perm_write" eval="True"/>
+        <field name="perm_create" eval="True"/>
+        <field name="perm_unlink" eval="True"/>
+    </record>
     </data>
 </odoo>
This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

summary=_("Case review overdue"),
note=_("Case %s is due for review. Last review: %s") % (case.name, case.last_review_date or _("Never")),
user_id=case.case_worker_id.id,
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cron creates duplicate overdue review activities daily

Medium Severity

The _cron_check_reviews method creates a new activity for every overdue case on each daily run without checking for existing activities. The "upcoming" section correctly deduplicates by searching for existing activities with a matching summary, but the "overdue" section has no such check. This causes a new "Case review overdue" activity to be created every day for each overdue case, flooding workers with duplicate tasks.

Fix in Cursor Fix in Web

raise ValidationError(
f"Stage '{case.stage_id.name}' requires minimum intensity level "
f"{case.stage_id.min_intensity}."
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stage requirement fields defined but never enforced

Medium Severity

The spp.case.stage model defines is_requires_assessment and is_requires_approval boolean fields, and these are exposed in stage configuration views. However, _check_stage_requirements() only validates is_requires_plan and min_intensity — it never checks the assessment or approval requirements. Administrators can configure stages to require these, but the system silently ignores those settings.

Additional Locations (1)

Fix in Cursor Fix in Web

"approved_date": fields.Datetime.now(),
}
)
return True
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plan approval skips state validation check

Medium Severity

The action_approve method on spp.case.intervention.plan sets the state to "approved" without verifying the plan is currently in "pending_approval" state. This allows approving plans directly from "draft", "active", "completed", or "revised" states, bypassing the intended workflow. Other action methods like action_activate and action_submit_for_approval correctly validate the current state before transitioning.

Fix in Cursor Fix in Web

Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep OSS found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the platform's capabilities by integrating a robust case management system. It provides tools for tracking cases through various stages, managing interventions, and linking with other core modules like programs and registries. Concurrently, it refines the existing Grievance Redress Mechanism (GRM) to improve usability and access control, ensuring a more streamlined and secure experience for managing complaints and feedback.

Highlights

  • New Case Management Modules: Introduced 12 new modules to establish a comprehensive case management system, covering base functionality, CEL rules for triage and assignment, demo data generation, and integrations with entitlements, graduation, programs, registry, and sessions.
  • GRM Module Enhancements: Updated the existing GRM module to align with stable upstream versions, incorporating new ticket workflow action buttons, refining officer record rules for separate create permissions, renaming the GRM privilege, converting HTML fields to plain text, and defaulting ticket assignments to the current user.
Changelog
  • spp_case_base/README.rst
    • Added documentation for the OpenSPP Case Management Base module.
  • spp_case_base/init.py
    • Added initialization for the base case management module.
  • spp_case_base/manifest.py
    • Added manifest file for the base case management module.
  • spp_case_base/data/ir_cron.xml
    • Added cron job for checking case review schedules.
  • spp_case_base/models/init.py
    • Added initialization for case management models.
  • spp_case_base/models/case.py
    • Added the core case management model with various fields and methods.
  • spp_case_base/models/case_assessment.py
    • Added the case assessment model for evaluations and risk assessments.
  • spp_case_base/models/case_config.py
    • Added configuration models for risk factors, vulnerabilities, closure reasons, and teams.
  • spp_case_base/models/case_intervention.py
    • Added the case intervention model for individual interventions within a plan.
  • spp_case_base/models/case_intervention_plan.py
    • Added the case intervention plan model for managing intervention plans.
  • spp_case_base/models/case_note.py
    • Added the case note model for documenting case activities.
  • spp_case_base/models/case_referral.py
    • Added the case referral model for tracking external service referrals.
  • spp_case_base/models/case_stage.py
    • Added the case stage model for defining workflow stages.
  • spp_case_base/models/case_type.py
    • Added the case type model for categorizing cases.
  • spp_case_base/models/case_visit.py
    • Added the case visit model for tracking client contacts.
  • spp_case_base/pyproject.toml
    • Added pyproject.toml for build system configuration.
  • spp_case_base/readme/DESCRIPTION.md
    • Added detailed description for the base case management module.
  • spp_case_base/security/case_security.xml
    • Added security placeholder for backward compatibility.
  • spp_case_base/security/compliance.yaml
    • Added compliance specification for access control configuration.
  • spp_case_base/security/groups.xml
    • Added security groups for case management roles.
  • spp_case_base/security/ir.model.access.csv
    • Added model access rules for case management models.
  • spp_case_base/security/privileges.xml
    • Added privilege definition for case management.
  • spp_case_base/security/rules.xml
    • Added record rules for case management models.
  • spp_case_base/static/description/index.html
    • Added HTML description for the base case management module.
  • spp_case_base/tests/init.py
    • Added initialization for case management tests.
  • spp_case_base/tests/test_case.py
    • Added test cases for the main case model.
  • spp_case_base/tests/test_case_intervention.py
    • Added test cases for the intervention model.
  • spp_case_base/tests/test_case_intervention_plan.py
    • Added test cases for the intervention plan model.
  • spp_case_base/tests/test_case_security.py
    • Added test cases for case management security rules.
  • spp_case_base/tests/test_compliance_generated.py
    • Added auto-generated compliance tests for access control.
  • spp_case_base/views/case_activity_views.xml
    • Added views for case visits, notes, and referrals.
  • spp_case_base/views/case_assessment_views.xml
    • Added views for case assessments.
  • spp_case_base/views/case_config_views.xml
    • Added views for case configuration models.
  • spp_case_base/views/case_intervention_views.xml
    • Added views for intervention plans and interventions.
  • spp_case_base/views/case_menus.xml
    • Added menu items for case management modules.
  • spp_case_base/views/case_stage_views.xml
    • Added views for case stages.
  • spp_case_base/views/case_type_views.xml
    • Added views for case types.
  • spp_case_base/views/case_views.xml
    • Added main views for the case model.
  • spp_case_cel/README.rst
    • Added documentation for the OpenSPP Case Management: CEL Rules module.
  • spp_case_cel/init.py
    • Added initialization for the CEL rules module.
  • spp_case_cel/manifest.py
    • Added manifest file for the CEL rules module.
  • spp_case_cel/models/init.py
    • Added initialization for CEL rules models.
  • spp_case_cel/models/case.py
    • Extended the case model to apply triage and assignment rules.
  • spp_case_cel/models/case_assignment_rule.py
    • Added the case assignment rule model using CEL expressions.
  • spp_case_cel/models/case_triage_rule.py
    • Added the case triage rule model using CEL expressions.
  • spp_case_cel/pyproject.toml
    • Added pyproject.toml for build system configuration.
  • spp_case_cel/readme/DESCRIPTION.md
    • Added detailed description for the CEL rules module.
  • spp_case_cel/security/ir.model.access.csv
    • Added model access rules for CEL rules models.
  • spp_case_cel/static/description/index.html
    • Added HTML description for the CEL rules module.
  • spp_case_cel/tests/init.py
    • Added initialization for CEL rules tests.
  • spp_case_cel/tests/test_assignment_rules.py
    • Added test cases for assignment rules.
  • spp_case_cel/tests/test_triage_rules.py
    • Added test cases for triage rules.
  • spp_case_cel/views/case_assignment_rule_views.xml
    • Added views for case assignment rules.
  • spp_case_cel/views/case_cel_menus.xml
    • Added menu items for CEL rules configuration.
  • spp_case_cel/views/case_triage_rule_views.xml
    • Added views for case triage rules.
  • spp_case_demo/README.md
    • Added markdown documentation for the OpenSPP Case Management Demo module.
  • spp_case_demo/README.rst
    • Added reStructuredText documentation for the OpenSPP Case Management Demo module.
  • spp_case_demo/init.py
    • Added initialization for the demo data module.
  • spp_case_demo/manifest.py
    • Added manifest file for the demo data module.
  • spp_case_demo/data/case_stages.xml
    • Added default case stages for demo purposes.
  • spp_case_demo/data/case_types.xml
    • Added default case types for demo purposes.
  • spp_case_demo/models/init.py
    • Added initialization for demo data models.
  • spp_case_demo/models/case_demo_stories.py
    • Added fixed demo stories for case management scenarios.
  • spp_case_demo/models/generate_cases.py
    • Added logic for generating random and story-based cases.
  • spp_case_demo/pyproject.toml
    • Added pyproject.toml for build system configuration.
  • spp_case_demo/readme/DESCRIPTION.md
    • Added detailed description for the demo data module.
  • spp_case_demo/security/ir.model.access.csv
    • Added model access rules for demo data generator and wizard.
  • spp_case_demo/static/description/index.html
    • Added HTML description for the demo data module.
  • spp_case_demo/views/case_demo_wizard_view.xml
    • Added views for the case demo wizard.
  • spp_case_demo/wizard/init.py
    • Added initialization for the demo wizard.
  • spp_case_demo/wizard/case_demo_wizard.py
    • Added the case demo wizard model.
  • spp_case_entitlements/README.rst
    • Added documentation for the OpenSPP Case Entitlements Integration module.
  • spp_case_entitlements/init.py
    • Added initialization for the entitlements integration module.
  • spp_case_entitlements/manifest.py
    • Added manifest file for the entitlements integration module.
  • spp_case_entitlements/models/init.py
    • Added initialization for entitlements integration models.
  • spp_case_entitlements/models/case.py
    • Extended the case model with entitlement tracking.
  • spp_case_entitlements/pyproject.toml
    • Added pyproject.toml for build system configuration.
  • spp_case_entitlements/readme/DESCRIPTION.md
    • Added detailed description for the entitlements integration module.
  • spp_case_entitlements/security/ir.model.access.csv
    • Added model access rules for entitlements integration.
  • spp_case_entitlements/static/description/index.html
    • Added HTML description for the entitlements integration module.
  • spp_case_entitlements/views/case_views.xml
    • Added views to integrate entitlements into case forms and lists.
  • spp_case_graduation/README.rst
    • Added documentation for the OpenSPP Case Management: Graduation Integration module.
  • spp_case_graduation/init.py
    • Added initialization for the graduation integration module.
  • spp_case_graduation/manifest.py
    • Added manifest file for the graduation integration module.
  • spp_case_graduation/models/init.py
    • Added initialization for graduation integration models.
  • spp_case_graduation/models/case.py
    • Extended the case model to include graduation tracking.
  • spp_case_graduation/models/graduation_assessment.py
    • Extended the graduation assessment model to link to cases.
  • spp_case_graduation/pyproject.toml
    • Added pyproject.toml for build system configuration.
  • spp_case_graduation/readme/DESCRIPTION.md
    • Added detailed description for the graduation integration module.
  • spp_case_graduation/security/ir.model.access.csv
    • Added model access rules for graduation integration.
  • spp_case_graduation/static/description/index.html
    • Added HTML description for the graduation integration module.
  • spp_case_graduation/views/case_views.xml
    • Added views to integrate graduation info into case forms.
  • spp_case_graduation/views/graduation_views.xml
    • Added views to integrate case links into graduation assessment forms.
  • spp_case_programs/README.md
    • Added markdown documentation for the OpenSPP Case Programs Integration module.
  • spp_case_programs/README.rst
    • Added reStructuredText documentation for the OpenSPP Case Programs Integration module.
  • spp_case_programs/init.py
    • Added initialization for the programs integration module.
  • spp_case_programs/manifest.py
    • Added manifest file for the programs integration module.
  • spp_case_programs/models/init.py
    • Added initialization for programs integration models.
  • spp_case_programs/models/case.py
    • Extended the case model with program enrollment tracking.
  • spp_case_programs/pyproject.toml
    • Added pyproject.toml for build system configuration.
  • spp_case_programs/readme/DESCRIPTION.md
    • Added detailed description for the programs integration module.
  • spp_case_programs/security/ir.model.access.csv
    • Added model access rules for programs integration.
  • spp_case_programs/static/description/index.html
    • Added HTML description for the programs integration module.
  • spp_case_programs/tests/init.py
    • Added initialization for programs integration tests.
  • spp_case_programs/tests/test_case_programs.py
    • Added test cases for program integration.
  • spp_case_programs/views/case_views.xml
    • Added views to integrate program info into case forms and lists.
  • spp_case_registry/README.rst
    • Added documentation for the OpenSPP Case Registry Integration module.
  • spp_case_registry/init.py
    • Added initialization for the registry integration module.
  • spp_case_registry/manifest.py
    • Added manifest file for the registry integration module.
  • spp_case_registry/models/init.py
    • Added initialization for registry integration models.
  • spp_case_registry/models/case.py
    • Extended the case model with registry integration fields.
  • spp_case_registry/models/res_partner.py
    • Extended the res.partner model with case counts and actions.
  • spp_case_registry/pyproject.toml
    • Added pyproject.toml for build system configuration.
  • spp_case_registry/readme/DESCRIPTION.md
    • Added detailed description for the registry integration module.
  • spp_case_registry/security/ir.model.access.csv
    • Added model access rules for registry integration.
  • spp_case_registry/static/description/index.html
    • Added HTML description for the registry integration module.
  • spp_case_registry/views/case_views.xml
    • Added views to integrate registry fields into case forms and lists.
  • spp_case_registry/views/res_partner_views.xml
    • Added views to integrate case info into res.partner forms.
  • spp_case_session/README.rst
    • Added documentation for the OpenSPP Case Management: Session Integration module.
  • spp_case_session/init.py
    • Added initialization for the session integration module.
  • spp_case_session/manifest.py
    • Added manifest file for the session integration module.
  • spp_case_session/models/init.py
    • Added initialization for session integration models.
  • spp_case_session/models/case.py
    • Extended the case model to include session tracking.
  • spp_case_session/models/session.py
    • Extended the session model to link back to cases.
  • spp_case_session/pyproject.toml
    • Added pyproject.toml for build system configuration.
  • spp_case_session/readme/DESCRIPTION.md
    • Added detailed description for the session integration module.
  • spp_case_session/security/ir.model.access.csv
    • Added model access rules for session integration.
  • spp_case_session/static/description/index.html
    • Added HTML description for the session integration module.
  • spp_case_session/views/case_views.xml
    • Added views to integrate session info into case forms.
  • spp_case_session/views/session_views.xml
    • Added views to integrate case links into session forms.
  • spp_grm/README.rst
    • Updated maturity badge, GitHub link, and model descriptions.
    • Updated bug tracker and maintainer links.
  • spp_grm/manifest.py
    • Updated module version, website URL, development status, and maintainers list.
    • Removed res_config_settings_views.xml from data files.
  • spp_grm/controllers/grm_portal.py
    • Simplified route decorator for portal tickets.
    • Removed nosemgrep comments for sudo and unvalidated redirect.
  • spp_grm/data/grm_data.xml
    • Added stage_type fields to GRM ticket stages.
  • spp_grm/data/mail_templates.xml
    • Simplified HTML structure for ticket confirmation email template.
  • spp_grm/data/user_roles.xml
    • Renamed group_grm_user to group_grm_officer in user role assignments.
  • spp_grm/models/grm_sla_rule.py
    • Changed warning log message to use rule name instead of ID for SLA evaluation errors.
  • spp_grm/models/grm_ticket.py
    • Converted description and resolution_summary fields from Html to Text.
    • Simplified partner_id domain.
    • Added stage_type field and refined stage approval message.
    • Defaulted user_id to the current user if no team manager is assigned.
    • Added workflow action buttons for stage transitions (start progress, awaiting, resolve, cancel, reject, reopen).
  • spp_grm/security/compliance.yaml
    • Updated comments for group_grm_supervisor and group_grm_user.
  • spp_grm/security/grm_security.xml
    • Updated XML header for consistency.
  • spp_grm/security/groups.xml
    • Updated XML header.
    • Changed privilege_id from privilege_grm_user to privilege_grm.
    • Updated comments for group_grm_user.
  • spp_grm/security/privileges.xml
    • Renamed privilege_grm_user to privilege_grm.
  • spp_grm/security/rules.xml
    • Updated XML header.
    • Refined rule_spp_grm_ticket_officer permissions to remove create access.
    • Added rule_spp_grm_ticket_officer_create for separate create permission.
  • spp_grm/static/description/index.html
    • Updated maturity badge, GitHub link, and model descriptions.
    • Updated bug tracker and maintainer links.
  • spp_grm_case_link/README.rst
    • Added documentation for the OpenSPP GRM Case Link module.
  • spp_grm_case_link/init.py
    • Added initialization for the GRM case link module.
  • spp_grm_case_link/manifest.py
    • Added manifest file for the GRM case link module.
  • spp_grm_case_link/models/init.py
    • Added initialization for GRM case link models.
  • spp_grm_case_link/models/grm_ticket.py
    • Extended the GRM ticket model to link to cases.
  • spp_grm_case_link/pyproject.toml
    • Added pyproject.toml for build system configuration.
  • spp_grm_case_link/readme/DESCRIPTION.md
    • Added detailed description for the GRM case link module.
  • spp_grm_case_link/security/ir.model.access.csv
    • Added model access rules for GRM case link.
  • spp_grm_case_link/static/description/index.html
    • Added HTML description for the GRM case link module.
  • spp_grm_case_link/views/grm_ticket_views.xml
    • Added views to integrate case links into GRM ticket forms.
  • spp_grm_cel/README.rst
    • Added documentation for the OpenSPP GRM: CEL Rules module.
  • spp_grm_cel/init.py
    • Added initialization for the GRM CEL rules module.
  • spp_grm_cel/manifest.py
    • Added manifest file for the GRM CEL rules module.
  • spp_grm_cel/models/init.py
    • Added initialization for GRM CEL rules models.
  • spp_grm_cel/models/grm_escalation_rule.py
    • Added the GRM escalation rule model using CEL expressions.
  • spp_grm_cel/models/grm_ticket.py
    • Extended the GRM ticket model to apply escalation rules.
  • spp_grm_cel/pyproject.toml
    • Added pyproject.toml for build system configuration.
  • spp_grm_cel/readme/DESCRIPTION.md
    • Added detailed description for the GRM CEL rules module.
  • spp_grm_cel/security/ir.model.access.csv
    • Added model access rules for GRM escalation rules.
  • spp_grm_cel/static/description/index.html
    • Added HTML description for the GRM CEL rules module.
  • spp_grm_cel/tests/init.py
    • Added initialization for GRM CEL rules tests.
  • spp_grm_cel/tests/test_escalation_rules.py
    • Added test cases for GRM escalation rules.
  • spp_grm_cel/views/grm_cel_menus.xml
    • Added menu items for GRM CEL rules configuration.
  • spp_grm_cel/views/grm_escalation_rule_views.xml
    • Added views for GRM escalation rules.
  • spp_grm_programs/README.rst
    • Added documentation for the OpenSPP GRM Programs Integration module.
  • spp_grm_programs/init.py
    • Added initialization for the GRM programs integration module.
  • spp_grm_programs/manifest.py
    • Added manifest file for the GRM programs integration module.
  • spp_grm_programs/models/init.py
    • Added initialization for GRM programs integration models.
  • spp_grm_programs/models/grm_ticket.py
    • Extended the GRM ticket model with program integration fields.
  • spp_grm_programs/pyproject.toml
    • Added pyproject.toml for build system configuration.
  • spp_grm_programs/readme/DESCRIPTION.md
    • Added detailed description for the GRM programs integration module.
  • spp_grm_programs/security/ir.model.access.csv
    • Added model access rules for GRM programs integration.
  • spp_grm_programs/static/description/index.html
    • Added HTML description for the GRM programs integration module.
  • spp_grm_programs/views/grm_ticket_views.xml
    • Added views to integrate program info into GRM ticket forms.
  • spp_grm_registry/README.rst
    • Added documentation for the OpenSPP GRM Registry Integration module.
  • spp_grm_registry/init.py
    • Added initialization for the GRM registry integration module.
  • spp_grm_registry/manifest.py
    • Added manifest file for the GRM registry integration module.
  • spp_grm_registry/models/init.py
    • Added initialization for GRM registry integration models.
  • spp_grm_registry/models/grm_ticket.py
    • Extended the GRM ticket model with registry integration fields.
  • spp_grm_registry/models/res_partner.py
    • Extended the res.partner model with GRM ticket counts and actions.
  • spp_grm_registry/pyproject.toml
    • Added pyproject.toml for build system configuration.
  • spp_grm_registry/readme/DESCRIPTION.md
    • Added detailed description for the GRM registry integration module.
  • spp_grm_registry/security/ir.model.access.csv
    • Added model access rules for GRM registry integration.
  • spp_grm_registry/static/description/index.html
    • Added HTML description for the GRM registry integration module.
  • spp_grm_registry/views/grm_ticket_views.xml
    • Added views to integrate registry fields into GRM ticket forms.
  • spp_grm_registry/views/res_partner_views.xml
    • Added views to integrate GRM ticket info into res.partner forms.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@codecov
Copy link

codecov bot commented Mar 5, 2026

Codecov Report

❌ Patch coverage is 92.26619% with 86 lines in your changes missing coverage. Please review.
✅ Project coverage is 55.79%. Comparing base (a72ada0) to head (3fce171).
⚠️ Report is 14 commits behind head on 19.0.

Files with missing lines Patch % Lines
spp_case_base/models/case.py 80.00% 30 Missing ⚠️
spp_case_cel/models/case_triage_rule.py 83.11% 13 Missing ⚠️
spp_case_demo/models/generate_cases.py 94.14% 12 Missing ⚠️
spp_case_base/models/case_config.py 89.47% 6 Missing ⚠️
spp_case_base/models/case_type.py 79.31% 6 Missing ⚠️
spp_case_cel/models/case_assignment_rule.py 94.25% 5 Missing ⚠️
spp_case_base/models/case_assessment.py 96.15% 3 Missing ⚠️
spp_case_base/models/case_intervention_plan.py 96.47% 3 Missing ⚠️
spp_case_base/models/case_stage.py 90.47% 2 Missing ⚠️
spp_case_base/__manifest__.py 0.00% 1 Missing ⚠️
... and 5 more
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##             19.0      #68       +/-   ##
===========================================
- Coverage   71.36%   55.79%   -15.57%     
===========================================
  Files         319      162      -157     
  Lines       25878     9291    -16587     
===========================================
- Hits        18467     5184    -13283     
+ Misses       7411     4107     -3304     
Flag Coverage Δ
spp_aggregation ?
spp_api_v2 ?
spp_api_v2_change_request ?
spp_api_v2_cycles ?
spp_api_v2_data ?
spp_api_v2_entitlements ?
spp_api_v2_products ?
spp_api_v2_service_points ?
spp_base_common 90.26% <ø> (-2.55%) ⬇️
spp_case_base 91.68% <91.68%> (?)
spp_case_cel 89.01% <89.01%> (?)
spp_case_demo 94.34% <94.34%> (?)
spp_case_entitlements 97.61% <97.61%> (?)
spp_case_graduation 97.77% <97.77%> (?)
spp_case_programs 97.14% <ø> (?)
spp_case_registry 98.33% <ø> (?)
spp_case_session 94.64% <ø> (?)
spp_gis_indicators 88.04% <ø> (?)
spp_graduation 93.45% <ø> (?)
spp_grm 63.22% <ø> (?)
spp_grm_case_link 99.03% <ø> (?)
spp_programs 45.43% <ø> (-4.14%) ⬇️
spp_security 66.66% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_case_base/__init__.py 100.00% <100.00%> (ø)
spp_case_base/models/__init__.py 100.00% <100.00%> (ø)
spp_case_base/models/case_intervention.py 100.00% <100.00%> (ø)
spp_case_base/models/case_note.py 100.00% <100.00%> (ø)
spp_case_base/models/case_referral.py 100.00% <100.00%> (ø)
spp_case_base/models/case_visit.py 100.00% <100.00%> (ø)
spp_case_cel/__init__.py 100.00% <100.00%> (ø)
spp_case_cel/models/__init__.py 100.00% <100.00%> (ø)
spp_case_demo/__init__.py 100.00% <100.00%> (ø)
spp_case_demo/models/__init__.py 100.00% <100.00%> (ø)
... and 59 more

... and 258 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive case management module (spp_case_base) and several extension modules for CEL-based rules, demo data, and integration with programs, registry, and graduation. The changes are extensive and well-structured. My review focuses on improving performance by addressing N+1 query patterns, fixing some bugs in security checks and cron jobs, and enhancing code clarity and correctness in several models. Overall, this is a significant and valuable feature addition.

Note: Security Review did not run due to the size of the PR.

Comment on lines +103 to +111
@api.constrains("condition_cel")
def _check_condition_cel(self):
"""Validate CEL expression syntax."""
for rule in self:
if rule.condition_cel:
try:
pass
except Exception as e:
raise ValidationError(_("Invalid CEL expression in rule '%s': %s") % (rule.name, str(e))) from e

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The _check_condition_cel constraint is intended to validate the CEL expression, but the try block is empty. This means no validation is actually performed. You should call the CEL parser here to catch syntax errors when a rule is saved.

Suggested change
@api.constrains("condition_cel")
def _check_condition_cel(self):
"""Validate CEL expression syntax."""
for rule in self:
if rule.condition_cel:
try:
pass
except Exception as e:
raise ValidationError(_("Invalid CEL expression in rule '%s': %s") % (rule.name, str(e))) from e
@api.constrains("condition_cel")
def _check_condition_cel(self):
"""Validate CEL expression syntax."""
for rule in self:
if rule.condition_cel:
try:
if P:
P.parse(rule.condition_cel)
except Exception as e:
raise ValidationError(_("Invalid CEL expression in rule '%s': %s") % (rule.name, str(e))) from e

Comment on lines +473 to +480
existing = self.env["mail.activity"].search(
[
("res_model", "=", "spp.case"),
("res_id", "=", case.id),
("summary", "ilike", "review upcoming"),
],
limit=1,
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The search for an existing activity uses a hardcoded string "review upcoming" while the summary is created using a translatable string _("Case review upcoming"). This will cause the check to fail in any language other than English. The search should use the translated string as well, or a non-translatable key should be used for identification. Using an exact match with = is also more performant.

Suggested change
existing = self.env["mail.activity"].search(
[
("res_model", "=", "spp.case"),
("res_id", "=", case.id),
("summary", "ilike", "review upcoming"),
],
limit=1,
)
existing = self.env["mail.activity"].search(
[
("res_model", "=", "spp.case"),
("res_id", "=", case.id),
("summary", "=", _("Case review upcoming")),
],
limit=1,
)

Comment on lines +110 to +119
@api.constrains("condition_cel")
def _check_condition_cel(self):
"""Validate CEL expression syntax."""
for rule in self:
if rule.condition_cel:
try:
# Basic syntax validation
pass
except Exception as e:
raise ValidationError(_("Invalid CEL expression in rule '%s': %s") % (rule.name, str(e))) from e

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The _check_condition_cel constraint is empty and does not perform any validation on the CEL expression. This should be implemented to catch syntax errors early, when the rule is saved.

Suggested change
@api.constrains("condition_cel")
def _check_condition_cel(self):
"""Validate CEL expression syntax."""
for rule in self:
if rule.condition_cel:
try:
# Basic syntax validation
pass
except Exception as e:
raise ValidationError(_("Invalid CEL expression in rule '%s': %s") % (rule.name, str(e))) from e
@api.constrains("condition_cel")
def _check_condition_cel(self):
"""Validate CEL expression syntax."""
for rule in self:
if rule.condition_cel:
try:
if P:
P.parse(rule.condition_cel)
except Exception as e:
raise ValidationError(_("Invalid CEL expression in rule '%s': %s") % (rule.name, str(e))) from e

Comment on lines +217 to +219
supervisor_group = self.env.ref("spp_case_base.group_case_supervisor", raise_if_not_found=False)
if supervisor_group and supervisor_group not in self.env.user.group_ids:
raise UserError("Only supervisors can review assessments.")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The group membership check supervisor_group not in self.env.user.group_ids is not the recommended way to check for permissions in Odoo. It's better to use self.env.user.has_group('spp_case_base.group_case_supervisor'), which correctly handles group inheritance and is the idiomatic approach.

Suggested change
supervisor_group = self.env.ref("spp_case_base.group_case_supervisor", raise_if_not_found=False)
if supervisor_group and supervisor_group not in self.env.user.group_ids:
raise UserError("Only supervisors can review assessments.")
if not self.env.user.has_group("spp_case_base.group_case_supervisor"):
raise UserError("Only supervisors can review assessments.")

Comment on lines +175 to +200
def _get_worker_with_lowest_caseload(self, team):
"""Find the team member with the lowest active caseload.

Args:
team: spp.case.team record

Returns:
res.users record or False
"""
Case = self.env["spp.case"]
best_worker = False
lowest_count = float("inf")

for member in team.member_ids:
case_count = Case.search_count(
[
("case_worker_id", "=", member.id),
("is_active", "=", True),
]
)

if case_count < self.max_cases_per_worker and case_count < lowest_count:
lowest_count = case_count
best_worker = member

return best_worker

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The _get_worker_with_lowest_caseload method iterates through team members and executes a search_count for each, which can lead to performance issues (N+1 queries) on teams with many members. This can be optimized by using a single read_group to fetch the caseloads for all members at once.

    def _get_worker_with_lowest_caseload(self, team):
        """Find the team member with the lowest active caseload."""
        if not team.member_ids:
            return False

        case_data = self.env["spp.case"].read_group(
            [
                ("case_worker_id", "in", team.member_ids.ids),
                ("is_active", "=", True),
            ],
            ["case_worker_id"],
            ["case_worker_id"],
        )
        caseload_map = {item["case_worker_id"][0]: item["case_worker_id_count"] for item in case_data}

        best_worker = False
        lowest_count = float("inf")

        for member in team.member_ids:
            case_count = caseload_map.get(member.id, 0)
            if case_count < self.max_cases_per_worker and case_count < lowest_count:
                lowest_count = case_count
                best_worker = member

        return best_worker

"author": "OpenSPP",
"website": "https://github.com/OpenSPP/openspp-modules",
"license": "LGPL-3",
"development_status": "Stable",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The development_status is set to "Stable", but this contradicts the "Alpha" maturity badge and the explicit warning in the README.rst file. To avoid confusion and set correct expectations for users, this should be aligned with the alpha status mentioned in the documentation.

Suggested change
"development_status": "Stable",
"development_status": "Alpha",

Comment on lines +181 to +190
if assessment.risk_score is False or assessment.risk_score < 0:
assessment.risk_level = "low"
elif assessment.risk_score <= 25:
assessment.risk_level = "low"
elif assessment.risk_score <= 50:
assessment.risk_level = "medium"
elif assessment.risk_score <= 75:
assessment.risk_level = "high"
else:
assessment.risk_level = "critical"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic to compute risk_level is a bit complex. The condition assessment.risk_score is False will always be false for a Float field, which defaults to 0.0. Since the _check_risk_score constraint prevents negative values, the logic can be simplified for better readability and correctness.

Suggested change
if assessment.risk_score is False or assessment.risk_score < 0:
assessment.risk_level = "low"
elif assessment.risk_score <= 25:
assessment.risk_level = "low"
elif assessment.risk_score <= 50:
assessment.risk_level = "medium"
elif assessment.risk_score <= 75:
assessment.risk_level = "high"
else:
assessment.risk_level = "critical"
for assessment in self:
score = assessment.risk_score or 0.0
if score <= 25:
assessment.risk_level = "low"
elif score <= 50:
assessment.risk_level = "medium"
elif score <= 75:
assessment.risk_level = "high"
else:
assessment.risk_level = "critical"

number = fields.Char(string="Ticket number", default="/", readonly=True)
name = fields.Char(string="Title", required=True)
description = fields.Html(required=True, sanitize_style=True)
description = fields.Text(required=True)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The description field has been changed from Html to Text. This is a significant change that could lead to loss of formatting for existing data and may affect how information is displayed in the portal and backend. Was this change intentional?

Comment on lines +294 to 297
resolution_summary = fields.Text(
string="Resolution Summary",
sanitize_style=True,
help="Detailed summary of the resolution",
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The resolution_summary field has been changed from Html to Text. This will remove rich text formatting capabilities. If detailed, formatted summaries are needed, this should be reverted to Html.

Comment on lines +19 to +45
<!-- GRM Officer: Read/write own tickets or team tickets -->
<record id="rule_spp_grm_ticket_officer" model="ir.rule">
<field name="name">GRM Ticket: Officer Own and Team Tickets</field>
<field ref="model_spp_grm_ticket" name="model_id"/>
<field name="domain_force">[
'|',
('user_id', '=', user.id),
('team_id.member_ids', 'in', [user.id])
]</field>
<field name="groups" eval="[Command.link(ref('group_grm_officer'))]" />
<field name="perm_read" eval="True" />
<field name="perm_write" eval="True" />
<field name="perm_create" eval="True" />
<field name="perm_unlink" eval="False" />
</record>
<field name="groups" eval="[Command.link(ref('group_grm_officer'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>

<!-- GRM Supervisor: Team tickets they supervise -->
<record id="rule_spp_grm_ticket_supervisor" model="ir.rule">
<field name="name">GRM Ticket: Supervisor Supervised Team Tickets</field>
<field ref="model_spp_grm_ticket" name="model_id" />
<field name="domain_force">[
<!-- GRM Officer: Can create any ticket -->
<record id="rule_spp_grm_ticket_officer_create" model="ir.rule">
<field name="name">GRM Ticket: Officer Create</field>
<field ref="model_spp_grm_ticket" name="model_id"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[Command.link(ref('group_grm_officer'))]"/>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
</record>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The record rule for GRM officers has been split into two: one for read/write access with a domain restricting to own/team tickets, and another for create access with a global domain [(1, '=', 1)]. This is a good practice for fine-grained control, allowing officers to create tickets for anyone but only edit a subset. However, the perm_create on the first rule should be False to avoid ambiguity. The new rule_spp_grm_ticket_officer_create correctly handles the creation permission.

Add test suites for spp_case_demo (108 tests), spp_grm_case_link (78
tests), spp_grm_programs (42 tests), spp_grm_registry (35 tests),
spp_case_session (29 tests), spp_case_registry (32 tests),
spp_case_entitlements (32 tests), spp_case_graduation (22 tests),
and expand spp_case_programs (6 → 16 tests).
# Check if current user is a supervisor
supervisor_group = self.env.ref("spp_case_base.group_case_supervisor", raise_if_not_found=False)
if supervisor_group and supervisor_group not in self.env.user.group_ids:
raise UserError("Only supervisors can review assessments.")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Supervisor group check uses wrong field for inheritance

Medium Severity

The action_review method checks supervisor_group not in self.env.user.group_ids to verify supervisor permissions. The test suite uses all_group_ids (not group_ids) to verify inherited group membership, and the compliance tests use has_group(). Using group_ids may not include groups inherited through implied_ids, potentially blocking managers from reviewing assessments. The idiomatic approach is self.env.user.has_group().

Fix in Cursor Fix in Web

These modules were missing from git but required by spp_case_graduation
and spp_case_session, causing CI test failures.
- Replace deprecated <data noupdate="1"> with <odoo noupdate="1">
- Split mixed noupdate XML files into groups + rules files
- Add priority=99 to views using position="replace"
- Wrap error strings in _() for translation compliance
- Fix "OpenSPP" -> "OpenSPP.org" author in manifests
- Replace self.name with self.id in log to avoid PII
- Apply auto-fixes from prettier, oca-gen-addon-readme, manifest-website
Format XML files in spp_graduation and spp_session_tracking with
prettier. Remove res_config_settings_views.xml from spp_grm as it
references non-existent helpdesk_mgmt fields and is not in the manifest.
… modules

- Change development_status from "Stable" to "Production/Stable" (C8111)
- Remove redundant string= parameters matching auto-generated names (W8113)
- Use named translation placeholders instead of positional %s (W8120)
- Fix development_status "Stable" to "Production/Stable" in 9 more manifests
- Remove redundant string= parameters in 10 more model files (W8113)
- Wrap message_post body strings in _() for translation (C8107)
- Convert absolute imports to relative in spp_grm_demo tests (W8150)
- Add nosemgrep annotations for legitimate sudo() in demo data (semgrep)
- Regenerate README.rst and index.html after status changes
@@ -548,8 +536,8 @@

for i, note in enumerate(notes):
note_date = ticket_date + timedelta(days=int((i + 1) * resolution_days / (len(notes) + 1)))
ticket.sudo().message_post( # nosemgrep: odoo-sudo-without-context — demo data generation runs as admin
body=f"<p>{note.get('text', '')}</p>",
ticket.sudo().message_post(

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users
@@ -754,8 +729,8 @@
note_date = ticket.create_date + timedelta(days=days_offset)

# Post message
ticket.sudo().message_post( # nosemgrep: odoo-sudo-without-context — demo data generation runs as admin
body=f"<p>{step}</p>",
ticket.sudo().message_post(

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users
@@ -773,8 +748,8 @@
escalation = scenario.get("escalation", {})
case_type = escalation.get("case_type", "general_investigation")

ticket.sudo().message_post( # nosemgrep: odoo-sudo-without-context — demo data generation runs as admin
body=f"<p>Ticket escalated to case management ({case_type})</p>",
ticket.sudo().message_post(

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

"approved_date": fields.Datetime.now(),
}
)
return True
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plan approval workflow missing source state guards

Medium Severity

action_approve has no state check at all, allowing a plan in any state (including draft or completed) to be directly approved. Similarly, action_submit_for_approval checks for interventions but not the current state, so a completed or revised plan could be re-submitted. Compare with action_activate which correctly guards with if plan.state != "approved".

Additional Locations (1)

Fix in Cursor Fix in Web

Demo data generators legitimately need sudo() to create records across
models. Exclude spp_case_demo and spp_grm_demo from semgrep scanning
(matching existing spp_4ps_demo exclusion) instead of inline nosemgrep
annotations.
"case_worker_id": self.env.user.id,
}

case = self.env["spp.case"].sudo().create(vals)

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users
limit=1,
)
if intervention:
intervention.sudo().write({"state": "completed"})

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users
Add nosemgrep annotations for legitimate sudo() usage in non-demo
modules (sequence generation, SLA breach handling, mail templates,
portal ticket creation, CEL rule counters). Fix redirect nosemgrep
prefix and ruff-format in generate_cases.py.
action_date = fields.Date.today() - timedelta(days=days_back)

if action == "create_plan":
current_plan = Plan.sudo().create(

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users
)

elif action == "add_intervention" and current_plan:
Intervention.sudo().create(

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users
intervention.sudo().write({"state": "completed"})
elif action in ("home_visit", "office_visit", "final_visit"):
visit_type = "home" if action != "office_visit" else "office"
Visit.sudo().create(

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users
)

elif action == "progress_note":
Note.sudo().create(

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users
)

elif action in ("assessment", "emergency_assessment", "safety_assessment"):
Note.sudo().create(

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users

for _i in range(random.randint(1, 3)):
visit_date = intake_date + timedelta(days=random.randint(5, 60))
Visit.sudo().create(

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users

for _i in range(random.randint(1, 4)):
note_date = intake_date + timedelta(days=random.randint(1, 60))
Note.sudo().create(

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users

if closure_stage:
closure_date = intake_date + timedelta(days=random.randint(30, 90))
case.sudo().write(

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users
CaseType = self.env["spp.case.type"]
case_type = CaseType.search([("name", "=", type_name)], limit=1)
if not case_type:
case_type = CaseType.sudo().create(

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users
Service = self.env["spp.service"]
service = Service.search([("name", "=", service_name)], limit=1)
if not service:
service = Service.sudo().create(

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: semgrep.odoo-sudo-without-context Warning

sudo() bypasses all access controls. Ensure this is: Intentional and documented Using minimal scope (e.g., .sudo().read(['field']) not .sudo()) Not exposing sensitive data to unauthorized users
- Enable test_compliance_generated (1190 lines at 0% coverage) by adding
  import to tests/__init__.py
- Fix 21 wrong menu visibility assertions for unrestricted menus (no
  groups= attribute) from assertFalse to assertTrue
- Fix _menu_visible helper to check group_ids with has_group() for
  proper implied group resolution instead of unreliable search([])
- Add test_case_models.py with 41 tests covering case_assessment,
  case_referral, case_note, and case_visit model methods
CI only tests changed modules, so project-wide coverage drops on PRs
that don't re-test every module. Fix by:
- Enabling carryforward flags so untested module coverage persists
- Setting project check to only_pulls mode
- Ignoring test files and migrations from coverage metrics
- Setting patch coverage target to 70%
@gonzalesedwin1123 gonzalesedwin1123 merged commit 69e0e5a into 19.0 Mar 5, 2026
30 of 31 checks passed
@gonzalesedwin1123 gonzalesedwin1123 deleted the push-modules-set-to-stable branch March 5, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant